package Dungeon;

import SquarePackage.Direction;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;

/**
 * Class for the position in the axes. A 3D system which only has positive coordinates.
 * 
 * @author 	Brecht Reynders and Florian Hendrickx
 * @version	1.0
 */
public class Dimension {
	/**
	 * Constructor for the dimensions(positions) of our Dungeon
	 * @param x	
	 * 			the x-coordinate
	 * @param y	
	 * 			the y-coordinate
	 * @param z	
	 * 			the z-coordinate
	 * @throws illegalArgumentException
	 * 			The given coordinates were not all valid
	 * 			| (!isValidDimension(x)) || !isValidDimension(y) || !isValidDimension(z))
	 */
	@Raw
	public Dimension(long x, long y, long z) throws IllegalArgumentException{
		if(!canHaveAsDimension(x, y, z))
            throw new IllegalArgumentException("one of the given coordinate values is not valid.");
		this.X = x;
		this.Y = y;
		this.Z = z;
//		this.dungeon = dungeon;
	}
	
	
	
//	/**
//	 * The identifying dungeon used to speccify the max coordinates
//	 */
//	private Dungeon dungeon;
//	/**
//	 * getter for the dungeon identifier
//	 * @return	Dungeon dungeon
//	 * 			the dungeon from which this coordinate depends
//	 */
//	
//	public Dungeon getDungeon() {
//		return dungeon;
//	}
//	/**
//	 * setter to for the dungeon object
//	 * @param dungeon
//	 * 			the new dungeon to set
//	 * @post	new.getDungeon=dungeon
//	 * @throws	IllegalArgumentException
//	 * 			the coordinates of this dimension are invalid caused by the max coordinates
//	 */
//	public void setDungeon(Dungeon dungeon) {
//		if(getYPosition()>dungeon.getMaxDimension().getYPosition()||getXPosition()>dungeon.getMaxDimension().getXPosition()||getZPosition()>dungeon.getMaxDimension().getZPosition())
//		{
//			throw new IllegalArgumentException("the coordinates of this dimension are invalid caused by the max coordinates");
//		}
//		this.dungeon = dungeon;
//	}

	/**
	 * Variables registering the position.
	 */
	private final long X;
	private final long Y;
	private final long Z;
	/**
	 * getter to return the x-coordinaat
	 * @return the x-coordinaat
	 */
	@Basic @Immutable @Raw
	public final long getXPosition(){
		return X;
	}
	/**
	 * getter to return the y-coordinaat
	 * @return the y-coordinaat
	 */
	@Basic @Immutable @Raw
	public final long getYPosition(){
		return Y;
	}
	/**
	 * getter to return the z-coordinaat
	 * @return the z-coordinaat
	 */

	@Basic @Immutable @Raw
	public final long getZPosition(){
		return Z;
	}
	/**
	 * equals method specified for dimension so we can check if a dimension actually means the same position
	 * @param dimension
	 * 			the dimension to compare with this dimension
	 * @return	False if the position to compare to this position is not effective.
	 * 			| if(other==null)
	 * 				result==false
	 * @return	True if and only if the x y and z coordinates are all the same.
	 * 			| if((dimension.getXPosition()==this.getXPosition()) && ...
	 * 					result == true
	 */			
	@Override
	public boolean equals(Object dimension){
		if(dimension==null)
			return false;
		return (((Dimension) dimension).getXPosition()==getXPosition() && ((Dimension) dimension).getYPosition()==getYPosition()&& ((Dimension) dimension).getZPosition()==getZPosition());
	}
    /** 
     * Checker for a coordinate
     * 
     * @param       coordinate
     *              the coordinate to check 
     * @return      True if and only if the coordinate is positive
     *                      | result == (coordinate >= 0)
     */
    public static boolean isValidCoordinate(long coordinate){
            if(coordinate < 0 || coordinate >= Long.MAX_VALUE)
                    return false;
            return true;
    }
    /**
     * Checker for the dimensions
     * @param	xCoordinate,yCoordinate,zCoordinate
     * 			the coordinates to check for creating a dimension
     */
    public boolean canHaveAsDimension(long x, long y, long z){
    	if((! isValidCoordinate(x)) || (! isValidCoordinate(y)) || (! isValidCoordinate(z))){
    		return false;
    	}
    	return true;
    }
    /**
     * method to check if two dimension are neighbours
     * @param dimension
     * 			the neigbour to check against this dimension
     * @return	true if and only if only one of the coordinates is differente
     */
    public boolean isNeigbour(Dimension dimension){
    	if(this.equals(dimension)||dimension==null){
    		return false;
    	}
    	long tempX = Math.abs(getXPosition()-dimension.getXPosition());
    	long tempY = Math.abs(getYPosition()-dimension.getYPosition());
    	long tempZ = Math.abs(getZPosition()-dimension.getZPosition());
    	if((tempX+tempY+tempZ)!=1){
    		return false;
    	}
    	if(tempX==1){
    		return true;
    	}
    	if(tempY==1){
    		return true;
    	}
    	if(tempZ==1){
    		return true;
    	}
    	return false;
    }
    /**
     * Method which is probably useful to print information about a specific dimension
     */
    public String toString(){
    	return "" + getXPosition() + "" + getYPosition() + "" + getZPosition() + ""; 
    }
    /**
     * Method to find the direction of a position relative to the direction of another one, when we know it is a neighbour
     */
    public Direction getNeigbourDirection(Dimension dimension){
    	if(equals(dimension)|| !isNeigbour(dimension)){
    		return null;
    	}
    	if((dimension.getXPosition()==getXPosition())&& (dimension.getYPosition()==getYPosition())){
    		if(dimension.getZPosition() == getZPosition()-1){
    			return Direction.FLOOR;
    	}
    		else{
    		return Direction.CEILING;
    			}
    	}
    	if((dimension.getYPosition()==getYPosition())&& (dimension.getZPosition()==getZPosition())){
    		if(dimension.getXPosition() == getXPosition()-1){
   				return Direction.WEST;
   			}
   			else{
   				return Direction.EAST;
   				}   
    	}
    	if((dimension.getXPosition()==getXPosition())&& (dimension.getZPosition()==getZPosition())){
    		if(dimension.getYPosition() == getYPosition()+1){
   				return Direction.SOUTH;
   			}
   			else{
   				return Direction.NORTH;
   				}   
    	}
    	return null;
    }
    /**
     * we override the hashcode methode so that two dimension objects who are equal also have the same hashcode
     * @param	dimension
     * 			the dimension to generate hashCode for. 
     * @return	the generated hashcode
     * 			| result == ((int) (dimension.getXPosition()*1000 +dimension.getYPosition()*100 + dimension.getZPosition()*10);

     */
    @Override
    public int hashCode(){
    	return (int) (getXPosition()*1009 +getYPosition()*101 + getZPosition()*11);
    }
    
    /**
     * method to get a Dimension in a certain specified direction called upon a dimension object
     */
    public Dimension getDimensionIn(Direction direction){
     	Dimension returnDimension;
    	if(direction == Direction.NORTH){
    		if(!isValidCoordinate(getYPosition()-1)){
    			returnDimension = null;
    		}
    		else{
    		returnDimension = new Dimension(getXPosition(),getYPosition()-1,getZPosition());
    		}
    	}
    	else if(direction == Direction.SOUTH){
    		returnDimension = new Dimension(getXPosition(),getYPosition()+1,getZPosition());
    	}
    	else if(direction == Direction.WEST){
    		if(!isValidCoordinate(getXPosition()-1)){
    			returnDimension = null;
    		}
    		else{
    		returnDimension = new Dimension(getXPosition()-1,getYPosition(),getZPosition());
    		}
    	}
    	else if(direction == Direction.EAST){
    		returnDimension = new Dimension(getXPosition()+1,getYPosition(),getZPosition());
    	}
    	else if(direction == Direction.CEILING){
    		returnDimension = new Dimension(getXPosition(),getYPosition(),getZPosition()+1);
    	}
    	else{
    		if(!isValidCoordinate(getZPosition()-1)){
    			returnDimension = null;
    		}
    		else{
    		returnDimension = new Dimension(getXPosition(),getYPosition(),getZPosition()-1);
    		}
    	}
		return returnDimension;
    	}
    	
    /**
     * Adds the coordinate values of the given dimension to this dimension. 
     * 
     * @param 	dimension
     * 			The given dimension to add to this dimension.
     * @return	The dimension in an absolute coordinate system.
     * 			| result = Dimension(getXPosition() + dimension.getXPosition(), getYPosition() + dimension.getYPosition(), getZPosition() + dimension.getZPosition())
     */
     public Dimension add(Dimension dimension){
    	 return new Dimension(getXPosition() + dimension.getXPosition(), getYPosition() + dimension.getYPosition(), getZPosition() + dimension.getZPosition());
     	 }
     /**
      * Subtracts the coordinate values of the given dimension of this this dimension.
      * @param 	dimension
      * 		The dimension to subtract.
      * @return	The dimension in a relative coordinate system.
      * 		| result = Dimension(getXPosition() - dimension.getXPosition(), getYPosition() - dimension.getYPosition(), getZPosition() - dimension.getZPosition())
      */
     public Dimension subtract(Dimension dimension){
    	 return new Dimension(getXPosition() - dimension.getXPosition(), getYPosition() - dimension.getYPosition(), getZPosition() - dimension.getZPosition());
     }
     
     /**
      * Check whether the given value is part of the balk with this as maximum dimension.
      * 
      * @param 	dimension
      * 		The extreme parameter of the balk.
      * @return	True if and only if all the values of this dimension are not smaller than the given dimension.
      * 		| result = dimension.getXPosition() <= getXPosition() && dimension.getYPosition() <= getYPosition() && dimension.getZPosition() <= getZPosition()
      */
     public boolean isSmallerThan(Dimension dimension){
    	 return dimension.getXPosition() <= getXPosition() && dimension.getYPosition() <= getYPosition() && dimension.getZPosition() <= getZPosition();
     }
     
     /**
      * Check whether the given value is part of the balk with this as maximum dimension.
      * 
      * @param 	dimension
      * 		The extreme parameter of the balk.
      * @return	True if and only if all the values of this dimension are larger are equal than the given dimension.
      * 		| result = dimension.getXPosition() >= getXPosition() && dimension.getYPosition() >= getYPosition() && dimension.getZPosition() >= getZPosition()
      */
     public boolean isLargerThan(Dimension dimension){
    	 return dimension.getXPosition() >= getXPosition() && dimension.getYPosition() >= getYPosition() && dimension.getZPosition() >= getZPosition();
     }
     
}	

